import { drizzle } from "drizzle-orm/mysql2";
import mysql from "mysql2/promise";
import { getTableColumns, sql, count as countColumn } from "drizzle-orm";

/**
 * 连接管理
 *
 * #### 例如
 * ```
 * {
 *  default: {
 *    // 连接池
 *    pool: x,
 *    // drizzle-orm db
 *    db: x
 *  }
 * }
 * ```
 */
let connectionManagers = {};

function parseMysqlUrlString(urlString) {
  const urlObj = new URL(urlString);
  return {
    host: urlObj.hostname,
    user: urlObj.username,
    password: urlObj.password,
    port: Number(urlObj.port || "3306"),
    database: urlObj.pathname.slice(1),
  };
}

/**
 * @typedef {Object} ConnectConfig
 * @property {string[]}  connections 连接地址
 * @property {boolean} [logger=false] - 是否开启日志, 默认: false
 */

/**
 *
 * @param {ConnectConfig} config 连接配置
 *
 * ```js
 * connect({
 *  connections: ['mysql://data:123456@192.168.0.1/x'],
 *  logger: true
 * })
 * ```
 */
export function connect(config) {
  if (Array.isArray(config.connections)) {
    for (let i = 0, len = config.connections.length; i < len; i++) {
      const connectItem = config.connections[i];
      // 是字符串连接
      if (typeof connectItem === "string") {
        const mysqlConfig = parseMysqlUrlString(connectItem);
        const connName = i === 0 ? "default" : mysqlConfig.database;
        const pool = mysql.createPool(mysqlConfig);
        connectionManagers[connName] = {
          pool,
          db: drizzle(pool, { logger: config.logger || false }),
        };
      }
    }
  }
}

/** 获取访问数据库 */
export function db(connectName = "default") {
  if (connectName in connectionManagers) {
    return connectionManagers[connectName].db;
  }
  throw new Error(`connectName ${connectName} not found`);
}

export function pool(name = "default") {
  if (name in connectionManagers) {
    return connectionManagers[name].pool;
  }
  throw new Error(`connectName ${name} not found`);
}

/**
 * 断开指定连接
 * @param {string} connectName 连接名称
 */
export function close(connectName = "default") {
  if (connectName in connectionManagers) {
    connectionManagers[connectName].pool.end();
    delete connectionManagers[connectName];
  }
}

/** 断开所有连接 */
export function closeAll() {
  for (const key in connectionManagers) {
    connectionManagers[key].pool.end();
  }
  connectionManagers = {};
}

/**
 * 返回实体对象的所有的字段列表字典
 *
 * @param schema 实体对象
 * @returns
 */
export function columns(schema) {
  const columns = {};
  for (const key of Object.keys(schema)) {
    columns[key] = schema[key];
  }
  return columns;
}

/**
 * 将分页数据转换为 mysql 的 limit, offset
 *
 * @param page 页码
 * @param pageSize 每一页数据
 * @returns
 */
export function pageLimit(page, pageSize) {
  let p = page;
  let ps = pageSize;
  if (page == null) {
    p = 1;
  }
  if (typeof page === "string") {
    p = Number(page);
  }
  if (pageSize == null) {
    ps = 10;
  }
  if (typeof pageSize === "string") {
    ps = Number(pageSize);
  }
  return {
    page: p,
    pageSize: ps,
    limit: ps,
    offset: (p - 1) * ps,
  };
}

/**
 * 根据提供的表格和列信息，构建一个用于更新MySQL表中冲突列的SQL查询。
 * 此函数用于处理 Upsert 操作中指定列的更新逻辑。
 *
 * @param table - 表的类型，必须继承自 MySqlTable 接口，用于获取表的结构信息。
 * @param columns - 可选参数，指定需要更新的列名数组。如果未提供，则默认为表的所有列。
 * @returns 返回一个对象，其中键为列名，值为SQL片段，用于构建更新语句。
 *
 * @throws 如果columns为空数组，则抛出错误。
 */
export const buildConflictUpdateColumns = (table, columns) => {
  let c = columns;
  // 如果未指定columns，则默认使用表的所有列。
  if (c == null) {
    c = Object.keys(table);
  }

  // 检查columns是否为空，为空则抛出错误。
  if (columns.length === 0) {
    throw new Error("columns is empty");
  }

  // 通过getTableColumns函数获取表的列信息。
  const cls = getTableColumns(table);

  // 使用reduce函数构建一个对象，其中每个属性对应一个列名，值为对应的SQL更新语句。
  return columns.reduce((acc, column) => {
    acc[column] = sql`values(${cls[column]})`;
    return acc;
  }, {});
};

/**
 * 插入或更新MySQL表中的数据。
 *
 * 本函数用于向指定的MySQL表插入数据。如果插入的数据中存在主键或唯一键重复的情况，
 * 则会更新这些重复键对应的列值。这提供了一种高效的方式来处理数据的插入和更新操作，
 * 避免了先查询再更新的额外开销。
 *
 * @param table 表的类型，用于确保类型安全和获取表结构信息。
 * @param values 要插入或更新的数据数组。每个元素应与表的结构对应。
 * @param updateColumns 在发生主键或唯一键重复时，指定要更新的列。如果未指定，则默认更新所有列。
 * @returns 返回一个Promise，解析为数据库操作的结果。
 */
export async function upsert(table, values, updateColumns) {
  // `.insert()` 用于插入新数据，`.onDuplicateKeyUpdate()` 用于指定在遇到重复键时执行的更新操作。

  return db()
    .insert(table)
    .values(values)
    .onDuplicateKeyUpdate({
      set: buildConflictUpdateColumns(table, updateColumns),
    });
}

/** 查询参数 */
// interface SelectOption {
//   /** 查询的表 */
//   table: MySqlTable;
//   /** 查询的列 */
//   columns?: object | string[];
//   /** 是否将查询结果按第一列平铺, true - 针对第一列平铺 */
//   flat?: boolean;
//   /** 查询条件 */
//   where?: SQL;
//   /** 数据库 */
//   db?: string;
//   orderBy?: SQL[];
//   limit?: number;
// }

/**
 * 执行 select 查询
 *
 * #### 1. 查询用户表
 * ```js
 * select({ table: User })
 * ```
 * #### 2. 查询用户表id=1, 按日期和时间倒序
 *
 * ```js
 * select({
 *  table: User,
 *  orderBy: [desc(User.date), desc(User.time)],
 *  where: eq(User.id, 1)
 * })
 * ```
 *
 * #### 3. 分组查询
 *
 * ```js
 *
 * select({
 *  table: Account,
 *  groupBy: Account.userId,
 *  flat: true
 * })
 * ```
 *
 * @param {Object} opts - 查询配置
 * @param {Object} opts.table - 查询的表
 * @param {Object[]} [opts.orderBy] - 排序
 * @param {Object} [opts.where] - 查询条件
 * @param {number} [opts.limit] - 查询数量
 * @param {number} [opts.offset] - 查询偏移
 * @param {string[]} [opts.columns] - 查询字段
 * @param {string | Object} [opts.groupBy] - 分组
 * @param {boolean} [opts.flat] - 是否将查询结果按第一列平铺, true - 针对第一列平铺
 * @returns
 */
export async function select(opts) {
  let columns = opts.columns;
  if (columns != null) {
    if (Array.isArray(columns)) {
      columns = columns.reduce((prev, column) => {
        prev[column] = opts.table[column];
        return prev;
      }, {});
    }
  }
  const res = [];
  let iter = db(opts.db).select(columns).from(opts.table).where(opts.where);
  if (opts.orderBy != null && opts.orderBy.length > 0) {
    iter = iter.orderBy(...opts.orderBy);
  }
  if (opts.limit != null) {
    iter = iter.limit(opts.limit);
  }
  if (opts.offset != null) {
    iter = iter.offset(opts.offset);
  }
  if (opts.groupBy != null) {
    if (typeof opts.groupBy === "string") {
      iter = iter.groupBy(opts.table[opts.groupBy]);
    } else {
      iter = iter.groupBy(opts.groupBy);
    }
  }
  for await (const row of iter.iterator()) {
    if (opts.flat === true) {
      let flatKey = "id";
      if (Array.isArray(opts.columns)) {
        flatKey = opts.columns[0];
      }
      res.push(row[flatKey]);
    } else {
      res.push(row);
    }
  }
  return res;
}

/**
 * 执行统计数量查询
 * @param {object} table        查询的表Model
 * @param {object} [where]      查询条件
 * @param {string} [connection] 查询的链接名称
 *
 * @returns {Promise<number>}
 */
export async function countQuery(table, where, connection) {
  const res = await db(connection)
    .select({ count: countColumn() })
    .from(table)
    .where(where);
  return res[0].count;
}
